package com.fanrui.code.gate.core;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.SqlSession;

import com.fanrui.code.dao.EParameterType;
import com.fanrui.code.dao.LoginInfoDao;
import com.fanrui.code.dao.ParameterConfigManager;
import com.fanrui.code.dao.UserDao;
import com.fanrui.code.entity.InvitationLog;
import com.fanrui.code.entity.InvitationLogExample;
import com.fanrui.code.entity.InvitationLogMapper;
import com.fanrui.code.entity.LoginInfo;
import com.fanrui.code.entity.User;
import com.fanrui.code.entity.UserExample;
import com.fanrui.code.entity.UserMapper;
import com.fanrui.code.gate.entity.Player;
import com.fanrui.code.util.CommandsUtil;
import com.fanrui.code.util.SessionUtil;
import com.fanrui.code.util.core.ListUtil;
import com.fanrui.code.util.core.VersionUtil;
import com.fanrui.code.util.logger.Stdout;
import com.fanrui.proto.cmd.CmdWrapper;
import com.fanrui.proto.cmd.CmdWrapper.CmdType;
import com.fanrui.proto.cmd.CmdWrapper.ResultCode;
import com.fanrui.proto.cmd.ReqLogin;
import com.fanrui.proto.cmd.ReqLogout;
import com.fanrui.proto.cmd.ResLogin;
import com.fanrui.proto.cmd.ResLogin.LoginResult;
import com.fanrui.proto.cmd.ResLogin.UpdateStatus;
import com.fanrui.proto.cmd.ServerCmdWrapper;

import io.netty.channel.Channel;

public class GateServerHandler {

	private static final int GATE_SERVER_MOCK_REQUEST_NUMBER = Integer.MIN_VALUE;

	// singleton
	private GateServerHandler() {
	}

	public static class GateServerSingleton {
		private static GateServerHandler instance = new GateServerHandler();
	}

	public static GateServerHandler getInstance() {
		return GateServerSingleton.instance;
	}

	public void handlerOtherServerMessage(ServerCmdWrapper msg) {
		long playerId = msg.getPlayerId();
		PlayerSession session = null;

		CmdWrapper cmdWrapper = msg.getCmdWrapper();
		if (cmdWrapper.getCmdType() == CmdWrapper.CmdType.ResLogout) {
			// 登出响应，用LogoutingSessions里面缓存的
			session = LoginOutSessions.getInstance().get(playerId);
		} else {
			session = Sessions.getInstance().get(playerId);
		}
		
		if(session == null){
			Stdout.GET().error("session设置出现异常");
			return;
		}
		if (cmdWrapper.getCmdType() == CmdWrapper.CmdType.ResLogin) {
			// 登录响应 里面需要 补充 是否提示更新
			ResLogin.Builder reslogin = (ResLogin.Builder) CommandsUtil.unwrapper(cmdWrapper).toBuilder();
			reslogin.setApStatus(UpdateStatus.Normal);
			reslogin.setAppUpdateUrl("");
			reslogin.setResult(LoginResult.Success);
			CmdWrapper.Builder cmdWrapperBuilder = cmdWrapper.toBuilder();
			cmdWrapper = cmdWrapperBuilder.setCmdData(reslogin.build().toByteString()).build();
			Stdout.GET().info("IP:{}登录成功,玩家ID为{}",session.getIp(),session.getPlayerId());
		}

		session.send(msg.getCmdWrapper());

		if (cmdWrapper.getCmdType() == CmdWrapper.CmdType.ResLogout) {
			// 如果是登出响应
			LoginOutSessions.getInstance().remove(session);
			session.close();
			Stdout.GET().info("玩家:{}  IP{}登出",session.getPlayerId(),session.getIp());
		}
	}

	public void receiveMessage(Channel channel, CmdWrapper msg) {
		// 已经登录过的
		PlayerSession loginOutSession = LoginOutSessions.getInstance().get(channel);
		if (loginOutSession != null) {
			if (loginOutSession != null) {
				Stdout.GET().warn("IP:{}-ID:{}在登出列表中不允许执行其他命令", loginOutSession.getIp(), loginOutSession.getPlayerId());
				channel.writeAndFlush(CommandsUtil.wrapper(msg.getCmdNumber(), CmdWrapper.ResultCode.LoginOut));
				return;
			}
		}

		if (msg.getCmdType() == CmdType.ReqLogin) { // 登录请求
			try {
				// 处理登录请求
				login(channel, msg);
			} catch (Exception e) {
				e.printStackTrace();
				channel.writeAndFlush(CommandsUtil.wrapper(msg.getCmdNumber(), CmdWrapper.ResultCode.Error));
			}
			return;

		} else {
			// 检查客户端是否登录过
			PlayerSession session = Sessions.getInstance().get(channel);
			if (session == null) {
				String ip = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
				Stdout.GET().warn("IP:{}尚未登录，不允许执行其他命令", ip);
				channel.writeAndFlush(CommandsUtil.wrapper(msg.getCmdNumber(), CmdWrapper.ResultCode.NotLogin));
				return;
			}
			session.refreshLastCommunicationTime();
			// 交给其他服务器进行处理
			// 通知大厅服务器Server登录。
			otherRequest(session, msg);
		}
	}

	private void otherRequest(PlayerSession session, CmdWrapper msg) {
		if (msg.getCmdType() == CmdWrapper.CmdType.ReqLogout) {
			Sessions.getInstance().remove(session);
			// 把用于登出的session放入LogoutingSessions
			LoginOutSessions.getInstance().put(session);
		}
		ServerCmdWrapper serverCmdWrapper = ServerCmdWrapper.newBuilder().setPlayerId(session.getPlayerId())
				.setCmdWrapper(msg).build();
		session.sendRequest(serverCmdWrapper);
	}

	/**
	 * 登录
	 * 
	 * @param channel
	 * @param msg
	 */
	private void login(Channel channel, CmdWrapper msg) {
		// 获取IP
		String ip = ((InetSocketAddress) channel.remoteAddress()).getAddress().getHostAddress();
		Stdout.GET().info("IP:{}请求登录", ip);
		// 获取登录信息
		ReqLogin req = (ReqLogin) CommandsUtil.unwrapper(msg);
		// 返回的指令序列号
		int reqNum = msg.getCmdNumber();
		PlayerSession session = Sessions.getInstance().get(channel);
		// 同一个客户端的多次登录请求 忽略
		if (session != null) {
			Stdout.GET().warn("IP:{}已经登录过了,禁止登录", ip);
			return;
		}
		// 检查版本号
		if (VersionUtil.getInstance().mustBeUpdate(req.getAppVersion())) {
			ResLogin.Builder reslogin = ResLogin.newBuilder();
			reslogin.setApStatus(UpdateStatus.ForceUpdate);
			reslogin.setAppUpdateUrl("www.baidu.com");
			reslogin.setResult(LoginResult.VersionErro);
			CmdWrapper rtn = CommandsUtil.wrapper(reqNum, reslogin.build());
			channel.writeAndFlush(rtn);
			Stdout.GET().info("IP:{}版本号不匹配,禁止登录", ip);
			return;
		}
		// 白名单检查

		// 封号检查

		// 如果是账号注册 检查短信验证码

		// 查找玩家 如果不存在的玩家就新建/注册玩家
		User u = findOrCreateUser(req.getAccount(), req.getPassword(), req.getNickName(), req.getHeadUrl(), channel, reqNum, ip,
				req.getDeviceModel(), req.getPlatTypeValue(), req.getAppVersion());
		if (null == u) {
			Stdout.GET().warn("IP:{}进行账号登录或创建时出现错误", ip);
			return;
		}
		int platTypeValue = req.getPlatTypeValue();
		String deviceModel = req.getDeviceModel();
		String appVersion = req.getAppVersion();
		long id = u.getId();
		LoginInfo loginInfo = LoginInfoDao.getInstance().createLoginInfo(ip, deviceModel, platTypeValue, appVersion,
				id);
		Player player = new Player(u, loginInfo);
		session = new PlayerSession(player.getId(), channel);
		PlayerSession oldSession = Sessions.getInstance().put(session);
		if (null != oldSession) {
			logout(oldSession);
		}
		// 通知大厅服务器Server登录。
		ServerCmdWrapper serverCmdWrapper = ServerCmdWrapper.newBuilder().setPlayerId(player.getId()).setCmdWrapper(msg)
				.build();
		session.sendRequest(serverCmdWrapper);
	}

	/**
	 * 获取一个登录用户
	 * 
	 * @return
	 */
	private User findOrCreateUser(String account, String password, String nickName, String headUrl, Channel channel, int req, String ip,
			String deviceModel, int platTypeValue, String appVersion) {
		ResLogin.Builder reslogin = ResLogin.newBuilder();
		reslogin.setApStatus(UpdateStatus.Normal);
		reslogin.setAppUpdateUrl("");
		try {
			if (StringUtils.isBlank(account) || StringUtils.isBlank(password)) {
				reslogin.setResult(LoginResult.AccountOrPasswordIsNull);// 账号无效
				Stdout.GET().info("IP:{}输入的账号或密码无效,登录失败", ip);
			} else {
				User user = UserDao.getInstance().findUserByAccount(account);
				if (null != user) { // 账号存在，判断密码是否正确
					if (!Player.checkPassWord(user.getPassword(), password)) {
						reslogin.setResult(LoginResult.PasswordErro);
						Stdout.GET().info("IP:{}输入的账号和密码不匹配,登录失败", ip);
					} else {
						if(!StringUtils.isBlank(nickName)) {
							user.setNickname(nickName);
							if(!StringUtils.isBlank(headUrl)) {
								user.setHeadUrl(headUrl);
							}
							UserDao.getInstance().save(user);
						}
						return user;
					}
				} else { // 账号不存在，创建一个新账号
					String invitationCode = generateActivation();
					user = createUser(account, password, nickName,invitationCode);
					if (null == user) {
						reslogin.setResult(LoginResult.CreateAccountErr);
						Stdout.GET().warn("IP:{}创建账号失败", ip);
					} else {
						// 创建保存新账号登录信息
//						LoginInfoDao.getInstance().createLoginInfo(ip, deviceModel, platTypeValue,
//								appVersion, user.getId());
//						UserDao.getInstance().save(user);
						return user;
					}
				}
			}
			CmdWrapper rtn = CommandsUtil.wrapper(req, reslogin.build());
			channel.writeAndFlush(rtn);
		} catch (Exception e) {
			CmdWrapper rtn = CommandsUtil.wrapper(req, ResultCode.Error);
			channel.writeAndFlush(rtn);
		}
		return null;
	}

	/**
	 *生成一个唯一邀请码
	 */
    public String generateActivation(){
        String random = RandomStringUtils.randomAlphanumeric(6);
        UserExample userExample = new UserExample();
        userExample.createCriteria().andInvitationCodeEqualTo(random);
        List<User> users = new ArrayList<>();
        try(SqlSession session = SessionUtil.getSession()) {
			UserMapper mapper = session.getMapper(UserMapper.class);
			users = mapper.selectByExample(userExample);
		} catch (Exception e) {
			e.printStackTrace();
		}
        if (users.size()<1){
            return random;
        }
        return generateActivation();
    }
    
	/**
	 * 给后台服务器发送client登出指令。
	 * 
	 * @param session
	 */
	public void logout(PlayerSession session) {
		// 把用于登出的session放入LogoutingSessions
		LoginOutSessions.getInstance().put(session);
		// 模拟客户端LogoutRequest
		ReqLogout logoutRequest = ReqLogout.newBuilder().build();
		CmdWrapper cmdWrapper = CommandsUtil.wrapper(GATE_SERVER_MOCK_REQUEST_NUMBER, logoutRequest); // 请求序列号是GATE_SERVER_MOCK_REQUEST_NUMBER，表示是Gate模拟的，不是从Client来的Logout。
		ServerCmdWrapper serverCmdWrapper = ServerCmdWrapper.newBuilder().setPlayerId(session.getPlayerId())
				.setCmdWrapper(cmdWrapper).build();
		session.sendRequest(serverCmdWrapper);
	}

	private User createUser(String account, String password, String nickName,String invitationCode) {
		return UserDao.getInstance().createUser(cerateBaseUser(account, password, nickName,invitationCode));
	}

	private static List<String> randomHeadUrl=null;
	private static List<String> randomName=null;
	private final static String defaultHeadUrl = "玩家";
	private final static String defaultNickName = "游客";
	
	/**
	 * 创建一个默认用户
	 * @param account
	 * @param password
	 * @param nickName
	 * @return
	 */
	private User cerateBaseUser(String account, String password, String nickName,String invitationCode) {
		if(randomHeadUrl == null){
			randomHeadUrl = ParameterConfigManager.getInstance().getStrListParameter(EParameterType.VisitorHeadUrl);
		}
		if(randomName == null){
			randomName = ParameterConfigManager.getInstance().getStrListParameter(EParameterType.VisitorName);
		}
		String headUrl = ListUtil.getRandom4List(randomHeadUrl);
		if(headUrl == null){
			headUrl = defaultHeadUrl;
		}
		if(nickName == null || StringUtils.isBlank(nickName)){
			nickName = defaultNickName + RandomUtils.nextInt(1, 10000);
		}
		User u = new User();
		Long randomId = null;
		while(true) {
			randomId = (long) ThreadLocalRandom.current().nextInt(100001, 999999);
			if(UserDao.getInstance().findUserById(randomId) == null) {
				break;
			}
		}
		u.setId(randomId);
		u.setAccount(account);
		u.setPassword(password);
		u.setHeadUrl(headUrl);
		u.setNickname(nickName);
		u.setInvitationCode(invitationCode);
		u.setGold(ParameterConfigManager.getInstance().getLongParameter(EParameterType.InitGold));
		u.setMoney(ParameterConfigManager.getInstance().getLongParameter(EParameterType.InitMoney));
		return u;
	}

	/**
	 * 处理客户端掉线
	 * 
	 * @param channel
	 */
	public void clientOffline(Channel channel) {
		PlayerSession session = Sessions.getInstance().get(channel); // 这里需要和login方法中的oldSession2保持相同。提前清除出Session集合
		if (session != null && session.isSameSocket(channel)) {
			Sessions.getInstance().remove(channel);
			// 通知MapServer登出
			logout(session);
		}
	}

	public void cleanTimeoutedSession(int timeoutTime) {
		for (PlayerSession session : Sessions.getInstance().allSessions()) {
			if (session.isTimeouted(timeoutTime)) {
				Sessions.getInstance().remove(session);
				Stdout.GET().info("{}超时,将其断开",session.getPlayerId());
				// 模拟登出
				logout(session);
			}
		}
	}

}
